package ch.ethz.fcl.overlays;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Projection;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
import ch.ethz.fcl.draw.GeoBounds;
import ch.ethz.fcl.udp.Protocal;
import ch.ethz.fcl.udp.UDPClient;

public class ScreenLineOverlay extends MetroBuzzOverlay {
	private final String TAG = "Screen Line Overlay";
	private int index;
	private UDPClient client;
	private int width;
	private int height;

	private GeoPoint geoStartPoint;
	private GeoPoint geoEndPoint;
	private GeoPoint geoCenterPoint;
	private PointF center = new PointF();
	Point pointStart = new Point();
	Point pointEnd = new Point();

	private PointF start = new PointF();
	private PointF mid = new PointF();
	private PointF old = new PointF();
	private PointF pivot = new PointF();

	private String mode;

	private float oldDist = 0;
	public static boolean cylinder = true;
	boolean hitLine = false;
	String msg = "MetroBuzz:Control:";

	public ScreenLineOverlay(Context context, int index) {
		super(context);
		this.index = index;
		client = new UDPClient();
		client.run();
	}

	public void init(GeoPoint geoPoint1, GeoPoint geoPoint2) {
		this.geoStartPoint = new GeoPoint(geoPoint1.getLatitudeE6(),
				geoPoint1.getLongitudeE6());
		this.geoEndPoint = new GeoPoint(geoPoint2.getLatitudeE6(),
				geoPoint2.getLongitudeE6());
		geoCenterPoint = new GeoPoint(
				(geoStartPoint.getLatitudeE6() + geoEndPoint.getLatitudeE6()) / 2,
				(geoStartPoint.getLongitudeE6() + geoEndPoint.getLongitudeE6()) / 2);
	}

	@Override
	public void draw(Canvas canvas, MapView mapView, boolean shadow) {
		width = canvas.getWidth();
		height = canvas.getHeight();
		Projection proj = mapView.getProjection();
		pointStart = proj.toPixels(geoStartPoint, pointStart);
		pointEnd = proj.toPixels(geoEndPoint, pointEnd);
		center.x = (pointStart.x + pointEnd.x) / 2;
		center.y = (pointStart.y + pointEnd.y) / 2;

		Paint paint = new Paint();
		paint.setColor(Color.BLUE);
		paint.setStrokeWidth(5.0f);
		canvas.drawLine(pointStart.x, pointStart.y, pointEnd.x, pointEnd.y,
				paint);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event, MapView mapView) {
		final int action = event.getAction();
		boolean result = false;
		Projection proj = mapView.getProjection();

		switch (action & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_DOWN:
			if (hitLine(event, mapView)) {
				start.x = event.getX();
				start.y = event.getY();
				hitLine = true;
				mode = Protocal.DRAG;
			}
			break;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_POINTER_UP:
			if (hitLine) {
				mode = Protocal.FINGERUP;
				hitLine = false;
				result = true;
				client.setMessage(msg + Protocal.FINGERUP + ":"
						+ Protocal.vpOperation + ":" + index);
				client.run();
			}
			break;
		case MotionEvent.ACTION_MOVE:
			if (hitLine) {
				if (mode == Protocal.DRAG) {
					float xDiff = event.getX() - start.x;
					float yDiff = event.getY() - start.y;
					GeoPoint eventGeo = (GeoPoint) proj.fromPixels(
							(int) event.getX(), (int) event.getY());
					GeoPoint startGeo = (GeoPoint) proj.fromPixels(
							(int) start.x, (int) start.y);
					start.set(event.getX(), event.getY());
					if (FloatMath.sqrt(xDiff * xDiff + yDiff * yDiff) > 10.0f) {
						drag(eventGeo, startGeo);
						client.setMessage(msg + Protocal.DRAG + ":"
								+ Protocal.vpOperation + ":" + index + ":"
								+ info());
					}
				} else if (mode.equals(Protocal.ZOOM)) {
					float newDist = spacing(event);
					if (newDist > 20.0f) {
						if (distPointF(pivot,
								new PointF(event.getX(0), event.getY(0))) < 10.0f) {
							// old arc tangent
							double oldYDiff = old.y - pivot.y;
							double oldXDiff = old.x - pivot.x;
							double oldAngle = Math.atan2(oldYDiff, oldXDiff
									/ xyRatio());

							// new arc tangent
							double newYDiff = event.getY(1) - pivot.y;
							double newXDiff = event.getX(1) - pivot.x;
							double newAngle = Math.atan2(newYDiff, newXDiff
									/ xyRatio());

							rotate(newAngle - oldAngle);
							old.set(event.getX(1), event.getY(1));
							client.setMessage(msg + Protocal.ROTATE + ":"
									+ Protocal.vpOperation + ":" + index + ":"
									+ info());
						} else if (Math.abs(newDist - oldDist) > 10.0f) {
							GeoPoint eventGeo0 = (GeoPoint) proj.fromPixels(
									(int) event.getX(0), (int) event.getY(0));
							GeoPoint eventGeo1 = (GeoPoint) proj.fromPixels(
									(int) event.getX(1), (int) event.getY(1));
							if (newDist > oldDist) {
								GeoPoint geoDiff = new GeoPoint(
										Math.abs(eventGeo1.getLatitudeE6()
												- eventGeo0.getLatitudeE6()),
										Math.abs(eventGeo1.getLongitudeE6()
												- eventGeo0.getLongitudeE6()));
								linLengthChange(geoDiff);
								client.setMessage(msg + Protocal.ZOOMOUT + ":"
										+ Protocal.vpOperation + ":" + index
										+ ":" + info());
							} else {
								GeoPoint geoDiff = new GeoPoint(
										-Math.abs(eventGeo1.getLatitudeE6()
												- eventGeo0.getLatitudeE6()),
										-Math.abs(eventGeo1.getLongitudeE6()
												- eventGeo0.getLongitudeE6()));
								linLengthChange(geoDiff);
								client.setMessage(msg + Protocal.ZOOMIN + ":"
										+ Protocal.vpOperation + ":" + index
										+ ":" + info());
							}
						}
					}
					oldDist = newDist;
				}
				result = true;
				client.run();
			}
			break;
		case MotionEvent.ACTION_POINTER_DOWN:
			if (hitLine) {
				oldDist = spacing(event);
				pivot.set(event.getX(0), event.getY(0));
				old.set(event.getX(1), event.getY(1));
				Log.d(TAG, "oldDist=" + oldDist);
				if (oldDist > 10.0f) {
					midPoint(mid, event);
					mode = Protocal.ZOOM;
				}
				result = true;
			}
		}
		mapView.postInvalidate();
		return (result || super.onTouchEvent(event, mapView));
	}

	private float distPointF(PointF p1, PointF p2) {
		float diffX = p2.x - p1.x;
		float diffY = p2.y - p1.y;

		return (float) Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2));
	}

	private boolean hitLine(MotionEvent event, MapView view) {
		GeoPoint eventGeo = (GeoPoint) view.getProjection().fromPixels(
				(int) event.getX(), (int) event.getY());
		float radius = disGeoPoint(view, geoStartPoint, geoEndPoint);
		float diff = disGeoPoint(view, eventGeo, geoCenterPoint);
		float r = radius > 100f ? radius : 100.0f; // set the minimum r
		r = r < 300f ? r : 300f; // set the maxmum r
		return diff < r;
	}

	private float disGeoPoint(MapView view, GeoPoint center, GeoPoint out) {
		Projection proj = view.getProjection();
		Point p1 = new Point();
		proj.toPixels(center, p1);

		Point p2 = new Point();
		proj.toPixels(out, p2);

		int sq_x = (p1.x - p2.x) * (p1.x - p2.x);
		int sq_y = (p1.y - p2.y) * (p1.y - p2.y);

		return (float) Math.sqrt(sq_x + sq_y);
	}

	private float spacing(MotionEvent event) {
		float x = event.getX(0) - event.getX(1);
		float y = event.getY(0) - event.getY(1);
		return FloatMath.sqrt(x * x + y * y);
	}

	private void midPoint(PointF point, MotionEvent event) {
		float x = event.getX(0) + event.getX(1);
		float y = event.getY(0) + event.getY(1);
		point.set(x / 2, y / 2);
	}

	private void linLengthChange(GeoPoint geo) {
		if (geoStartPoint.getLatitudeE6() < geoEndPoint.getLatitudeE6()) {
			geoStartPoint = new GeoPoint(
					(int) (geoStartPoint.getLatitudeE6() - 0.1f * geo
							.getLatitudeE6()),
					geoStartPoint.getLongitudeE6());

			geoEndPoint = new GeoPoint(
					(int) (geoEndPoint.getLatitudeE6() + 0.1f * geo
							.getLatitudeE6()),
					geoEndPoint.getLongitudeE6());
		}

		else if (geoStartPoint.getLatitudeE6() > geoEndPoint.getLatitudeE6()) {
			geoStartPoint = new GeoPoint(
					(int) (geoStartPoint.getLatitudeE6() + 0.1f * geo
							.getLatitudeE6()),
					geoStartPoint.getLongitudeE6());

			geoEndPoint = new GeoPoint(
					(int) (geoEndPoint.getLatitudeE6() - 0.1f * geo
							.getLatitudeE6()),
					geoEndPoint.getLongitudeE6());
		}

		if (geoStartPoint.getLongitudeE6() > geoEndPoint.getLongitudeE6()) {
			geoStartPoint = new GeoPoint(geoStartPoint.getLatitudeE6(),
					(int) (geoStartPoint.getLongitudeE6() + 0.1 * geo
							.getLongitudeE6()));

			geoEndPoint = new GeoPoint(geoEndPoint.getLatitudeE6(),
					(int) (geoEndPoint.getLongitudeE6() - 0.1 * geo
							.getLongitudeE6()));
		}

		else if (geoStartPoint.getLongitudeE6() < geoEndPoint.getLongitudeE6()) {
			geoStartPoint = new GeoPoint(geoStartPoint.getLatitudeE6(),
					(int) (geoStartPoint.getLongitudeE6() - 0.1 * geo
							.getLongitudeE6()));

			geoEndPoint = new GeoPoint(geoEndPoint.getLatitudeE6(),
					(int) (geoEndPoint.getLongitudeE6() + 0.1 * geo
							.getLongitudeE6()));
		}
	}

	private void drag(GeoPoint eventGeo, GeoPoint startGeo) {
		geoStartPoint = new GeoPoint(geoStartPoint.getLatitudeE6()
				+ eventGeo.getLatitudeE6() - startGeo.getLatitudeE6(),
				geoStartPoint.getLongitudeE6() + eventGeo.getLongitudeE6()
						- startGeo.getLongitudeE6());

		geoEndPoint = new GeoPoint(geoEndPoint.getLatitudeE6()
				+ eventGeo.getLatitudeE6() - startGeo.getLatitudeE6(),
				geoEndPoint.getLongitudeE6() + eventGeo.getLongitudeE6()
						- startGeo.getLongitudeE6());

		geoCenterPoint = new GeoPoint(geoStartPoint.getLatitudeE6() / 2
				+ geoStartPoint.getLatitudeE6() / 2,
				geoStartPoint.getLongitudeE6() / 2
						+ geoEndPoint.getLongitudeE6() / 2);
	}

	public void extend() {
		float xdiff = Math.abs(pointStart.x - pointEnd.x);
		float ydiff = Math.abs(pointStart.y - pointEnd.y);

		if (pointStart.x > pointEnd.x) {
			pointStart.x += 0.05f * xdiff;
			pointEnd.x -= 0.05f * xdiff;
		} else {
			pointStart.x -= 0.05f * xdiff;
			pointEnd.x += 0.05f * xdiff;
		}

		if (pointStart.y > pointEnd.y) {
			pointStart.y += 0.05f * ydiff;
			pointEnd.y -= 0.05f * ydiff;
		} else {
			pointStart.y -= 0.05f * ydiff;
			pointEnd.y += 0.05f * ydiff;
		}
		center.x = (pointStart.x + pointEnd.x) / 2;
		center.y = (pointStart.y + pointEnd.y) / 2;
	}

	public void compress() {
		float xdiff = Math.abs(pointStart.x - pointEnd.x);
		float ydiff = Math.abs(pointStart.y - pointEnd.y);

		if (pointStart.x > pointEnd.x) {
			pointStart.x -= 0.05f * xdiff;
			pointEnd.x += 0.05f * xdiff;
		} else {
			pointStart.x += 0.05f * xdiff;
			pointEnd.x -= 0.05f * xdiff;
		}

		if (pointStart.y > pointEnd.y) {
			pointStart.y -= 0.05f * ydiff;
			pointEnd.y += 0.05f * ydiff;
		} else {
			pointStart.y += 0.05f * ydiff;
			pointEnd.y -= 0.05f * ydiff;
		}
		center.x = (pointStart.x + pointEnd.x) / 2;
		center.y = (pointStart.y + pointEnd.y) / 2;
	}

	public void rotate(double delta) {
		double sin = Math.sin(delta * 2);
		double cos = Math.cos(delta * 2);

		int newLat = (int) (cos * geoStartPoint.getLatitudeE6() - sin
				* geoStartPoint.getLongitudeE6()
				- geoCenterPoint.getLatitudeE6() * cos
				+ geoCenterPoint.getLongitudeE6() * sin + geoCenterPoint
				.getLatitudeE6());
		int newLon = (int) (sin * geoStartPoint.getLatitudeE6() + cos
				* geoStartPoint.getLongitudeE6()
				- geoCenterPoint.getLatitudeE6() * sin
				- geoCenterPoint.getLongitudeE6() * cos + geoCenterPoint
				.getLongitudeE6());
		geoStartPoint = new GeoPoint(newLat, newLon);

		newLat = (int) (cos * geoEndPoint.getLatitudeE6() - sin
				* geoEndPoint.getLongitudeE6() - geoCenterPoint.getLatitudeE6()
				* cos + geoCenterPoint.getLongitudeE6() * sin + geoCenterPoint
				.getLatitudeE6());
		newLon = (int) (sin * geoEndPoint.getLatitudeE6() + cos
				* geoEndPoint.getLongitudeE6() - geoCenterPoint.getLatitudeE6()
				* sin - geoCenterPoint.getLongitudeE6() * cos + geoCenterPoint
				.getLongitudeE6());
		geoEndPoint = new GeoPoint(newLat, newLon);

		geoCenterPoint = new GeoPoint(geoStartPoint.getLatitudeE6() / 2
				+ geoEndPoint.getLatitudeE6() / 2,
				geoStartPoint.getLongitudeE6() / 2
						+ geoEndPoint.getLongitudeE6() / 2);
	}

	private String info() {
		float startY = GeoBounds.getBoundLat(geoStartPoint.getLatitudeE6());
		float startX = GeoBounds.getBoundLon(geoStartPoint.getLongitudeE6());

		float endY = GeoBounds.getBoundLat(geoEndPoint.getLatitudeE6());
		float endX = GeoBounds.getBoundLon(geoEndPoint.getLongitudeE6());

		return startX + ":" + startY + ":" + endX + ":" + endY;
	}

	public void sendInfo() {
		client.setMessage(msg + Protocal.DRAG + ":" + Protocal.vpOperation
				+ ":" + index + ":" + info());
		client.run();
	}

	private float xyRatio() {
		return width / height;
	}
}
